package com.mall.mina.config;

import com.mall.mina.util.AgreementConstant;
import com.mall.mina.util.AgreementUtil;
import com.mall.mina.util.CrcUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.future.ReadFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.FilterEvent;
import org.springframework.cglib.proxy.Callback;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * mina 服务器处理器
 */
@Slf4j
@Component
public class MinaServerHandler extends IoHandlerAdapter implements Callback {

    private static boolean REMOVE_STATUS=false;

    @Override
    public void sessionCreated(IoSession session) throws Exception {
        super.sessionCreated(session);
    }

    @Override
    public void sessionOpened(IoSession session) throws Exception {
        startThread();
        super.sessionOpened(session);
    }

    @Override
    public void sessionClosed(IoSession session) throws Exception {
        super.sessionClosed(session);
        String clientIP = ((InetSocketAddress)session.getRemoteAddress()).getAddress().getHostAddress();
        SessionUtil.ONLINE_SESSION.remove(SessionUtil.ONLINE_DEVICE.get(clientIP));
    }

    @Override
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
        super.sessionIdle(session, status);
    }

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
        super.exceptionCaught(session, cause);
    }

    @Override
    public void messageReceived(IoSession session, Object message){
        CustomPack pack = (CustomPack) message;
        byte[] content = pack.getContent();
        String clientIP = ((InetSocketAddress)session.getRemoteAddress()).getAddress().getHostAddress();

        //当前返回信息：错误数据
        if (content[0] == AgreementConstant.DATA_ERROR){
            byte[] len = pack.getLen();
            byte[] crc = pack.getCrc();
            byte[] args = AgreementUtil.assembleAgreement(len, content);
            byte[] crc16ab = CrcUtils.crc16ab(args);

            //将获取crc校验值与生成的校验值相比较，不同则丢弃数据
            if (crc[0] != crc16ab[0] || crc[1] != crc16ab[1]){
                return;
            }
            //存储错误指令
            String deviceId = AgreementUtil.byteArrayToHexString(AgreementUtil.disassembleDeviceId(content));
            String key = deviceId + AgreementUtil.byteArrayToHexString(new byte[]{content[5]});
            ResultUtil.DATA_ERROR.put(key,content);
            SessionUtil.ONLINE_SESSION.put(deviceId,session);
        } else {
            //当前数据具有完整的前导码，并且不是获取指纹数据，是一串完整的返回数据，进行crc校验，当前数据生成的crc校验值与传递过来的校验值不一致，将丢弃数据
            int num = 0;
            byte[] len = pack.getLen();
            byte[] crc = pack.getCrc();
            //当前数据进行crc校验，当前数据生成的crc校验值与传递过来的校验值不一致，将丢弃数据
            byte[] args = AgreementUtil.assembleAgreement(len, content);
            byte[] crc16ab = CrcUtils.crc16ab(args);
            //将获取crc校验值与生成的校验值相比较，不同则丢弃数据
            if (crc[0] != crc16ab[0] || crc[1] != crc16ab[1]){
                 return;
            }
            String deviceId = AgreementUtil.byteArrayToHexString(AgreementUtil.disassembleDeviceId(content));
            addLockId(deviceId,content[5]);
            String key = deviceId + AgreementUtil.byteArrayToHexString(new byte[]{content[5]});
            SessionUtil.ONLINE_SESSION.put(deviceId,session);
            //存储在线的设备ip 用于在设备断开连接的时候删除设备缓存
            SessionUtil.ONLINE_DEVICE.put(clientIP,deviceId);

            //当前接收的参数不是心跳包
            if (content[0] != AgreementConstant.HEART_BEAT){
                //当前回调数据不是心跳包，则存储 content（操作码 + 负载）
                ResultUtil.ONLINE_RESULT.put(key,content);
            }else{
                //避免后期改为单个心跳包 当前数据是心跳包，则存储 content（操作码 + 负载）
                ResultUtil.HEART_BEAT.put(key,content);
                addOpenLock(deviceId,content[5],AgreementUtil.byteArrayToHexString(new byte[]{content[6]}));
            }
        }
    }


    @Override
    public void messageSent(IoSession session, Object message) throws Exception {
        super.messageSent(session, message);
    }

    @Override
    public void inputClosed(IoSession session) throws Exception {
        super.inputClosed(session);
    }

    @Override
    public void event(IoSession session, FilterEvent event) throws Exception {
        super.event(session, event);
    }


    /**
     * 向指定设备发送消息
     * @param deviceIdBytes 设备id
     * @param lockIdBytes 锁id
     * @param message 消息内容
     */
    public synchronized byte[] send(byte[] deviceIdBytes,byte lockIdBytes,Object message){
        String deviceId = AgreementUtil.byteArrayToHexString(deviceIdBytes);
        IoSession session = SessionUtil.ONLINE_SESSION.get(deviceId);
        if (session == null) {
            return null;
        }
        String key = deviceId + AgreementUtil.byteArrayToHexString(new byte[]{lockIdBytes});
        WriteFuture write = null;
        try {
            CustomPack pack = (CustomPack) message;
            StopWatch stopWatch = new StopWatch();
            stopWatch.start("写用时");

            //将对象转换成16进制字节数组
            byte[] len = pack.getLen();
            byte[] content = pack.getContent();
            byte[] crc = pack.getCrc();
            IoBuffer buffer = IoBuffer.allocate(content.length + 6).setAutoExpand(true);
            buffer.put(len);
            buffer.put(content);
            buffer.put(crc);
            buffer.flip();
            //将字节数组写入
            write = session.write(buffer);
            write.awaitUninterruptibly(2000);
            stopWatch.stop();
            if (write.isWritten()) {
                //log.info("向指定客户端发送数据成功:" + session + "--发送数据：" + buffer.toString());
                session.getConfig().setUseReadOperation(true);
                stopWatch.start("读用时");
                ReadFuture read = session.read();
                read.awaitUninterruptibly(30000);
                if (read.isRead()) {
                    byte[] result;
                    byte[] dataError;
                    //获取返回数据
                    result = ResultUtil.ONLINE_RESULT.get(key);
                    dataError = ResultUtil.DATA_ERROR.get(key);

                    //返回信息是发送请求所回复的信息需要的时间（不包含获取的指纹信息）
                    int millis = 1000;
                    //当前返回数据指令是：下发/获取指纹所需要的时间（响应时间较长）
                    if (content[0] == AgreementConstant.FINGERPRINT_SET || content[0] == AgreementConstant.FINGERPRINT_GET){
                        millis = 3500;
                    }

                    for(int i=0;i<3;i++){
                        if (result == null && dataError == null) {
                            Thread.sleep(millis);
                            result = ResultUtil.ONLINE_RESULT.get(key);
                            dataError = ResultUtil.DATA_ERROR.get(key);
                        }else{
                            break;
                        }
                    }
                    //删除集合中当前命令返回数据
                    ResultUtil.ONLINE_RESULT.remove(key);
                    ResultUtil.DATA_ERROR.clear();
                    session.getConfig().setUseReadOperation(false);
                    if (result == null){
                        result = dataError;
                    }
                    return result;
                }
                stopWatch.stop();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    //新增在线锁id编号
    private void addLockId(String ip,byte lockIdByte){
        String lockId = AgreementUtil.byteToInt(lockIdByte)+"";
        List<String> lockList =  ResultUtil.LOCK_ID.get(ip);
        if(lockList!=null && lockList.size()>0){
            if(!lockList.contains(lockId)){
                lockList.add(lockId);
                ResultUtil.LOCK_ID.put(ip,lockList);
            }
        }else{
            lockList = new ArrayList<>();
            lockList.add(lockId);
            ResultUtil.LOCK_ID.put(ip,lockList);
        }
    }

    //保存开锁记录 用来轮询查设备开锁信息
    private void addOpenLock(String clientIP,byte lockIdByte,String type){
        // 1位 门状态 1开 0关 2位手柄状态 1开 0关
        String[] lockType = type.split("");
        int lockId = AgreementUtil.byteToInt(lockIdByte);
        String key = clientIP+","+lockId;
        String status = MinaServer.OPEN_LOCK.get(key);
        if("1".equals(lockType[1])){
            //状态为空或者已查询记录
            if(!StringUtils.hasLength(status)){
                MinaServer.OPEN_LOCK.put(key,"0");
            }
        }else{
            if("0".equals(status)){
                MinaServer.OPEN_LOCK.put(key,"1");
            }
        }
    }


    private void startThread(){
        if(REMOVE_STATUS){
            return;
        }
        REMOVE_STATUS = true;
        removeLockId();
    }

    //清楚记录的在线锁数据 锁id数据通过心跳保存 但是无法监听下线的锁 所以定时清理
    private void removeLockId(){
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                //锁设备最多接254把 对外限制最多32把 心跳包300毫秒一次
                for(String key: SessionUtil.ONLINE_SESSION.keySet()) {
                    ResultUtil.LOCK_ID.remove(key);
                }
            }
        },5,200, TimeUnit.SECONDS);
    }

    //获取在线设备id
    public List<String> onlineDevice(){
        List<String> onlineDevice = new ArrayList<>();
        //获取在线的ip对应的session
        for(String key: SessionUtil.ONLINE_SESSION.keySet()){
            onlineDevice.add(key);
        }
        return onlineDevice;
    }

    //根据网控器id获取网控器ip
    public String findIPByDeviceId(String deviceId){
        Iterator<Map.Entry<String, String>> iter = SessionUtil.ONLINE_DEVICE.entrySet().iterator();
        while(iter.hasNext()){
            Map.Entry<String, String> entry = iter.next();
            if(entry.getValue().equals(deviceId)){
                return entry.getKey();
            }
        }
        return "";
    }

}
